import { Portal } from "@/common/Portal";
import { stringNumFixToNum, toMap, treeForeach } from "@/util/common";
import com from "@/components/TreeSelector/TreeSelector.vue";
import { SimulateController } from "./Controller";
import { ElNotification, NotificationParams } from "element-plus";
import { MODEL_SERVER_URL, PropertyKey } from "@/constants/constants";
import { cloneDeep } from "lodash";
import {
  VariableData,
  SocketUpdateVariableMsg,
  SocketUpdateVariableMsgType,
  ConstraintVariationNotice,
  ContextMenuConfig,
  ConstraintInfo
} from "./_";
import { SimFrameToSubChannel, SubWinName, WinName } from "./types/Win";
import { VariableExportParam } from "./SimApi";
import { MessageChannelType } from "./types/Ipc";
import { ClientShowSimVariableData } from "./types/Socket";
import { EnumerationValue } from "./types/variable";
import { UIDataType, VarTableColumn } from "@/constants/types";
import { apiFunnel } from "@/util/apiFunnel";
import { variableChecking } from "@/util/checking";
import ContextMenu from "@/common/ContextMenu/index.vue";
import { ActionKey } from "@/action/ActionKey";
import { actionManager } from "@/action/ActionManager";

export default class VariableService {
  controller: SimulateController;
  treeSelectorPortal: Portal;

  columns: VarTableColumn[] = [
    { label: "name", name: "name", flex: true, alignCenter: true, hidden: false, lock: true },
    { label: "value", name: "value", hidden: false, lock: true },
    { label: "validationReq", name: "validationReq", flex: true, group: true, alignCenter: true, hidden: true },
    { label: "reqContent", name: "reqContent", flex: true, group: true, alignCenter: true, hidden: true },
  ];

  variableData: VariableData[] = [];
  variableDataMap = new Map<string, VariableData>(); // cache for show
  constraintStatusChange = new Map<string, string>();
  expandVariableRecord = new Set<string>();
  enumerationValuesMap = new Map<string, EnumerationValue[]>();
  selectedValueId = "";
  recordDynamicValueIdSceneIds = new Map<string, string[]>();

  /** 用于存放验证需求模型信息 */
  requirementMap = new Map<string, any>();

  constructor(controller: SimulateController) {
    this.controller = controller;
    this.treeSelectorPortal = new Portal(com);
  }

  private setVariableTreeDataToMap(vs: VariableData[], parentValueId?: string) {
    treeForeach(vs, (t: VariableData) => {
      if (parentValueId) {
        t.parentValueId = parentValueId;
      }
      this.variableDataMap.set(t.valueId, t);
    });
  }

  private async getAndWashSceneVariableData(sceneId: string) {
    const getRes =
      await this.controller.simApi.variableGetVariableBySceneSession({
        simulationSessionId: this.controller.sessionId,
        sceneId,
      });
    return this.washSceneVariableData(getRes?.data.data || []);
  }

  /**
   * 解析返回的验证需求和内容
   */
  private parseConstraintInfoList(list?: ConstraintInfo[]) {
    const validationReq: VariableData['validationReq'] = [];
    const reqContent: VariableData['reqContent'] = [];
    if (list && list.length) {
      for (let j = 0; j < list.length; j++) {
        const it = list[j];
        const requireModel = this.requirementMap.get(it.requirementId);
        validationReq!.push({
          name: requireModel?.name,
          value: requireModel?.id,
          modelId: requireModel?.id,
          icon: requireModel?.icon,
          valid: it.valid,
        });
        reqContent!.push({
          name: it.tempText,
          value: it.tempId,
          valid: it.valid,
        });
      }
    }
    return { validationReq, reqContent };
  }

  private async washSceneVariableData(variables: VariableData[]) {
    const idsRecord: string[] = [];
    treeForeach(variables, (t: VariableData) => {
      idsRecord.push(t.modelId);
      // 收集模型id，用于获取模型信息
      if (t.constraintInfoList) {
        t.constraintInfoList.forEach(it => {
          idsRecord.push(it.requirementId);
        });
      }
    });

    const modelInfosRes = await this.controller.modelApi.modelInfos({
      projectId: this.controller.projectId,
      modelIds: idsRecord,
    });

    const modelInfos = modelInfosRes?.data.data;
    // variable add model info
    if (modelInfos && modelInfos.length) {
      this.requirementMap = toMap(modelInfos);
      treeForeach(variables, (t) => {
        // add parentid
        if (t.children && t.children.length) {
          t.children.forEach((c) => {
            c.parentValueId = t.valueId;
          });
        }
        const variableChangedStatus = this.constraintStatusChange.get(
          t.valueId
        );
        if (variableChangedStatus) {
          t.status = variableChangedStatus;
        }

        const m = this.requirementMap.get(t.modelId);
        if (m) {
          t.disabled = true; // deal input disabled
          if (
            ["Integer", "Real", "Boolean", "String"].includes(t.valueType)
          ) {
            t.disabled = false;
            t.valueShow = true;
          }
          if (t.multiple) {
            t.name = `${m.name} [${t.valueList?.length}]`;
            t.disabled = true;
            t.valueShow = false;
          }
          t.modelVo = m; // add modelVo
          if (
            m.icon.startsWith("statics/") ||
              m.icon.startsWith(MODEL_SERVER_URL) ||
              m.icon.startsWith("/statics/")
          ) {
            // deal icon
            const _prefix =
                process.env.NODE_ENV === "development" ? "" : "app://./";
            t.icon = `${_prefix}${m.icon}`;
          } else {
            t.icon = `${MODEL_SERVER_URL}public/files/${this.controller.projectId}/${m.icon}`;
          }

          const { validationReq, reqContent } = this.parseConstraintInfoList(t.constraintInfoList);
          t.validationReq = validationReq;
          t.reqContent = reqContent;
        }
      });
    }
    return variables;
  }

  private async addRootToInitSceneVariable(v: VariableData[]) {
    const rootSceneData = this.controller.sceneService.sceneDataMap.get(
      this.controller.sceneId
    );
    if (!rootSceneData) return;
    const rootSceneModelInfoRes = await this.controller.modelApi.modelInfo({
      projectId: this.controller.projectId,
      modelId: rootSceneData!.instanceId,
    });
    const rootSceneModelInfo = rootSceneModelInfoRes.data.data;
    const leftNameIndex = rootSceneData?.name.indexOf("[");
    const rightNameIndex = rootSceneData?.name.indexOf("]");
    const rootVariableData = new VariableData({
      icon: rootSceneData?.icon,
      name: rootSceneModelInfo.name,
      sceneIds: [this.controller.sceneId],
      children: v,
      modelVo: rootSceneModelInfo,
      valueId: this.controller.sceneId,
      constraintMessage: "",
      export: false,
      multiple: false,
      hasChildren: true,
      expression: "",
      valueList: null,
      modelId: rootSceneData!.instanceId,
      ownerSceneId: "",
      status: "",
      value:
        rootSceneData?.name.substring(leftNameIndex! + 1, rightNameIndex) || "",
      valueType: "",
      variableType: "",
      disabled: true,
      expand: true,
      isRoot: true,
    });
    return rootVariableData;
  }

  serviceInit() {
    this.variableData.length = 0;
    this.variableDataMap.clear();
    this.constraintStatusChange.clear();
  }

  async initSceneVariable() {
    this.variableData.length = 0;
    this.variableDataMap.clear();
    const initSeceneData = await this.getAndWashSceneVariableData(
      this.controller.sceneId
    );
    const rootVariableData = await this.addRootToInitSceneVariable(
      initSeceneData
    );
    if (!rootVariableData) return;
    this.variableData.push(rootVariableData);
    this.setVariableTreeDataToMap(this.variableData);
    console.info("variable data: ", this.variableData);
  }

  async variableDataExpand(node: VariableData) {
    console.log("expand node: ", node);
    if (node.expand) {
      // record the expand node
      this.expandVariableRecord.add(node.valueId);
    } else {
      this.expandVariableRecord.delete(node.valueId);
    }
    let expandChildData: VariableData[] = [];
    if (!node.multiple) {
      let partpropertySceneId = "";
      if (!node?.valueList) {
        if (node.sceneIds) {
          // node.sceneIds may equal []
          partpropertySceneId = node.sceneIds![0];
        } else {
          const recordSceneIds = this.recordDynamicValueIdSceneIds.get(
            node.valueId
          );
          if (recordSceneIds) {
            partpropertySceneId = recordSceneIds[0];
          }
        }
      } else {
        partpropertySceneId =
          node.sceneIds![node?.valueList.indexOf(node.value as string)];
      }
      if (partpropertySceneId) {
        expandChildData = await this.getAndWashSceneVariableData(
          partpropertySceneId
        );
      } else {
        // has no-sceneId, signal related
        const res = await this.controller.simApi.variableGetVariableByValueId({
          simulationSessionId: this.controller.sessionId,
          valueId: node.valueId,
        });
        if (res.data?.data) {
          expandChildData = await this.washSceneVariableData(res.data.data);
        }
      }
    } else if (node.sceneIds?.length) {
      // partproperty multiple
      for (const [i, v] of node.sceneIds.entries()) {
        const nodeCopy = cloneDeep(node);
        nodeCopy.name = `[${i}]`;
        nodeCopy.valueId = v;
        if (node.valueList) {
          nodeCopy.value = node.valueList[i];
        }
        const multipleSceneVariable = await this.getAndWashSceneVariableData(v);
        nodeCopy.hasChildren = !!multipleSceneVariable.length;
        nodeCopy.children = [];
        nodeCopy.mindex = i;
        nodeCopy.expand = false;
        nodeCopy.multiple = false;
        nodeCopy.sceneIds = [v];
        nodeCopy.valueList = null;
        expandChildData.push(new VariableData(nodeCopy));
      }
    } else if (node.valueList?.length) {
      // valueproperty multiple
      for (const [i, v] of node.valueList.entries()) {
        const nodeCopy = cloneDeep(node);
        nodeCopy.name = `[${i}]`;
        nodeCopy.ownerSceneId = nodeCopy.valueId;
        nodeCopy.valueId = `${node.valueId}@@${i}`;
        nodeCopy.hasChildren = false;
        nodeCopy.value = v;
        nodeCopy.children = [];
        nodeCopy.mindex = i;
        nodeCopy.valueType = node.valueType;
        nodeCopy.disabled = false;
        expandChildData.push(new VariableData(nodeCopy));
      }
    }
    const expandNode = this.variableDataMap.get(node.valueId);
    /*
     * the gotten varaible data muse be pushed to prarent children first
     * and then add to the map
     * or the js-object refer-link will be broken
     */
    if (expandNode) {
      expandNode.children = expandChildData;
      this.setVariableTreeDataToMap(expandNode.children, expandNode.valueId);
    }
    /*
     * open the record expand
     */
    for (const valueId of this.expandVariableRecord.values()) {
      const variableDataMapData = this.variableDataMap.get(valueId);
      if (variableDataMapData && !variableDataMapData.expand) {
        variableDataMapData.expand = true;
        this.variableDataExpand(variableDataMapData);
      }
    }
  }

  async variableContextMenu(event: MouseEvent, row: VariableData, item: any, col: VarTableColumn) {
    this.selectedValueChange(row.valueId);
    let contextMenuItems = [];
    contextMenuItems.push(ContextMenuConfig[ActionKey.ExportInstance]);

    const addRemoveValueFlag = row.modelVo.baseSt && row.modelVo.baseSt.includes('PartProperty');
    ContextMenuConfig[ActionKey.AddValue].disabled = addRemoveValueFlag;
    ContextMenuConfig[ActionKey.RemoveValue].disabled = addRemoveValueFlag;
    if (item && item.modelId) {
      contextMenuItems.push(ContextMenuConfig[ActionKey.SelectOnTree]);
    }

    // handle add|remove
    if (row.value) { // filter partProperty
      contextMenuItems.push(ContextMenuConfig[ActionKey.RemoveValue]);
    } else {
      contextMenuItems.push(ContextMenuConfig[ActionKey.AddValue]);
    }
    
    // openTimeSeriesChart
    if (["Real", "Integer"].includes(row.valueType)) {
      // TODO bug: 时序图显示异常，暂时屏蔽开启时序图菜单
      // contextMenuItems.push(ContextMenuConfig.openTimeSeriesChart);
    }

    const isSupportExportInteractionRes = await this.controller.modelApi.modelCheckElementSupportEmulation({
      projectId: this.controller.projectId,
      modelId: row.modelVo.id
    });
    if (isSupportExportInteractionRes.data?.data) {
      contextMenuItems.push(ContextMenuConfig[ActionKey.ExportInteraction]);
    }

    if (row.modelVo.baseSt && row.modelVo.baseSt.includes("PartProperty")) {
      const modelinfo = await this.controller.modelApi.modelPropertyByKey({
        projectId: this.controller.projectId,
        modelId: row.modelVo.id,
        key: PropertyKey.TypedElement_type
      });
      ContextMenuConfig[ActionKey.ExportInteraction].disabled = !modelinfo?.value ? true : false;
      contextMenuItems.push(ContextMenuConfig[ActionKey.ExportInteraction]);
    }
    let menuX = 0;
    if (contextMenuItems.length && event.clientY + contextMenuItems.length * 40 < document.body.clientHeight) {
      menuX = event.clientY;
    } else {
      menuX = document.body.clientHeight - contextMenuItems.length * 40;
    }
    ContextMenu.popup({
      x: event.clientX,
      y: menuX,
      menuItems: contextMenuItems
    })
      .then((menuItem: any) => {
        const action = actionManager.getAction(ActionKey.VariableContextMenuFunc);
        if (action) {
          action.run(menuItem, row, item, col);
        }
      })
      .catch((err: any) => err && console.error(err));
  }

  selectedValueChange(vId: string) {
    if (this.selectedValueId) {
      const selectedValue = this.variableDataMap.get(this.selectedValueId);
      if (selectedValue && vId !== selectedValue?.valueId) {
        selectedValue!.isSelected = false;
      }
    }
    const selectingValue = this.variableDataMap.get(vId);
    this.selectedValueId = selectingValue!.valueId;
    selectingValue!.isSelected = true;
    console.log("selected variale: ", selectingValue);
  }

  async updateVariable(v: {
    valueId: string;
    value?: string | number | boolean;
    index?: number;
    ov: string | number | boolean;
  }) {
    // 开始校验变量值
    const variableData = this.variableDataMap.get(v.valueId);
    let curValue = v.value;
    if (variableData) {
      const { isSuccess, retValue } = variableChecking({
        valueType: variableData.valueType as UIDataType,
        value: v.value,
      });
      if (!isSuccess) {
        try {
          await app.$confirm.popup({
            single: true,
            title: this.controller.locale.localeTrans("err"),
            subTitle: this.controller.locale.localeTrans("in_invalid"),
            content: this.controller.locale.localeTrans("in_va_style"),
            cancelText: this.controller.locale.localeTrans("close"),
            okText: "",
            status: "error",
            width: 292,
          });
        } catch (e) {
          console.warn(e);
        }
        return false; // 校验不通过，阻断流程
      }
      curValue = retValue == null ? "" : String(retValue);
    }
    const i = v.index || 0;
    const p = {
      index: i,
      valueId: v.valueId,
      simulationSessionId: this.controller.sessionId,
      sceneId: this.controller.sceneId,
    };
    if (curValue) {
      (p as any).inputValue = curValue;
    }
    const updateRes = await this.controller.simApi.variableUpdate(p);
    if (updateRes?.data.code === 1000) {
      this.dealVariableModelPathAndSend(v.valueId);
      return true;
    }
    return false;
  }

  async exportToNew(exportedValueId: string, isRoot: boolean) {
    app.$loading.popup(this.controller.locale.localeTrans("ex_ins_ing"));
    let notificationParams = {} as NotificationParams;
    try {
      const queryPackagesRes =
        await this.controller.modelApi.modelSimulationQueryPackages(
          this.controller.projectId
        );
      if (queryPackagesRes.data?.code !== 1000) {
        throw new Error(queryPackagesRes.data.message);
      }
      const treePortalData = (await this.treeSelectorPortal.popup({
        treeData: [queryPackagesRes.data.data],
      })) as { selectedId: string };
      const p: VariableExportParam = {
        simulationSessionId: this.controller.sessionId,
        sceneSessionId: this.controller.sceneId,
        mountPoint: treePortalData.selectedId,
      };
      if (!isRoot) {
        p.valueId = exportedValueId;
      }
      const res = await this.controller.simApi.variableExport(p);
      if (res.data.code !== 1000) {
        throw new Error(res.data.message);
      }
      notificationParams = {
        title: this.controller.locale.localeTrans("ex_succ"),
        message: this.controller.locale.localeTrans("add_m_tree_al"),
        type: "success",
      };
    } catch (err: any) {
      if (err) {
        notificationParams = {
          title: this.controller.locale.localeTrans("ex_fail"),
          message: err.message || this.controller.locale.localeTrans("opr_error"),
          type: "warning",
        };
      }
    } finally {
      app.$loading.close();
    }
    if (Object.keys(notificationParams).length) {
      ElNotification(notificationParams);
    }
  }

  async exportAtExisted(modelId: string, valueId: string) {
    app.$loading.popup(this.controller.locale.localeTrans("ex_ins_ing"));
    let notificationParams = {} as NotificationParams;
    try {
      const InstancesByClassifierRes =
        await this.controller.modelApi.modelSimulationQueryInstancesByClassifier(
          {
            projectId: this.controller.projectId,
            classifierId: modelId,
          }
        );
      const treePortalData = (await this.treeSelectorPortal.popup({
        treeData: [InstancesByClassifierRes.data.data],
      })) as { selectedId: string };
      await this.controller.simApi.variableExportToExisted({
        simulationSessionId: this.controller.sessionId,
        sceneSessionId: this.controller.sceneId,
        valueId: valueId,
        mountPoint: treePortalData.selectedId,
      });
      notificationParams = {
        title: this.controller.locale.localeTrans("ex_succ"),
        message: this.controller.locale.localeTrans("data_add_al"),
        type: "success",
      };
    } catch (err) {
      notificationParams = {
        title: this.controller.locale.localeTrans("ex_fail"),
        message: this.controller.locale.localeTrans("opr_error"),
        type: "error",
      };
    }
    ElNotification(notificationParams);
    app.$loading.close();
  }

  async apiValueVariationNotice(vs: SocketUpdateVariableMsg[]) {
    for (const v of vs) {
      apiFunnel.addPool(v.valueId, async () => {
        const activeVariable = this.variableDataMap.get(v.valueId);
        const newVariableData = (
          await this.controller.simApi.variableGetVariableValueByValueId({
            simulationSessionId: this.controller.sessionId,
            valueId: v.valueId,
            sceneId: this.controller.sceneId,
          })
        ).data?.data;
        if (newVariableData && newVariableData.values) {
          for (const v of newVariableData.values) {
            if (v.valueType === UIDataType.Enumeration) {
              const enumValue =
                await this.controller.modelApi.modelPropertyByKey({
                  projectId: this.controller.projectId,
                  modelId: v.value,
                  key: PropertyKey.NamedElement_name,
                });
              if (enumValue) {
                v.value = enumValue.value;
              }
            }
          }
          /*
           * add scene data
           * now dynamic variable can get sceneIds
           */
          const mapVariableData = this.variableDataMap.get(v.valueId);
          if (mapVariableData && newVariableData.sceneIds) {
            this.recordDynamicValueIdSceneIds.set(v.valueId, [
              ...newVariableData.sceneIds,
            ]);
          }

          if (activeVariable) {
            switch (v.type) {
              case SocketUpdateVariableMsgType.REMOVE:
                activeVariable.value = "";
                break;
              case SocketUpdateVariableMsgType.INSERT:
                /*
                 *variableDataMap
                 * is outdate, need to update
                 */
                {
                  if (activeVariable.valueType !== "Object") {
                    const usedId =
                      activeVariable.parentValueId ||
                      activeVariable.ownerSceneId;
                    const parentVariable = this.variableDataMap.get(usedId);
                    if (parentVariable) {
                      await this.variableDataExpand(parentVariable);
                    }
                  } else {
                    const parentVariable = this.variableDataMap.get(
                      activeVariable.ownerSceneId
                    );
                    if (parentVariable) {
                      await this.variableDataExpand(parentVariable);
                    }
                  }
                }
                break;
              case SocketUpdateVariableMsgType.UPDATE:
                {
                  if (newVariableData.values.length === 1) {
                    const usedValue = newVariableData.values[0];
                    activeVariable.value = usedValue.value;
                    activeVariable.hasChildren = usedValue.hasChildren;
                    if (activeVariable.hasChildren) {
                      // this sceneIds exclude from scene-datas
                      activeVariable.sceneIds = usedValue.sceneIds;
                    }
                  } else if (newVariableData.values.length > 1) {
                    // multiple change
                    const recordMultipleValue = [];
                    for (const nv of newVariableData.values) {
                      recordMultipleValue.push(nv.value);
                    }
                    activeVariable.valueList = recordMultipleValue;
                    await this.variableDataExpand(activeVariable);
                  }
                  // not reasonable
                  this.dealVariableModelPathAndSend(v.valueId);
                }
                break;
              default:
            }
          }

          // send to uiFrame
          this.controller.winService.sendInfoToSubWins(
            {
              type: SimFrameToSubChannel.UI_Frame_Change,
              data: {
                propertyId: newVariableData.modelId,
                valuse: newVariableData.values,
                objectId: v.objectId,
              },
            },
            SubWinName.UIFrame
          );
        }
      });
    }
  }

  apiConstraintVariationNotice(v: ConstraintVariationNotice) {
    this.constraintStatusChange.set(v.valueId, v.status);
    const willChangedVariable = this.variableDataMap.get(v.valueId);
    if (willChangedVariable) {
      willChangedVariable.status = v.status;
      willChangedVariable.constraintMessage = v.constraintMessage;
      willChangedVariable.constraintInfoList = v.constraintInfoList || [];
      const { validationReq, reqContent } = this.parseConstraintInfoList(v.constraintInfoList);
      console.log('apiConstraintVariationNotice', validationReq, reqContent);
      willChangedVariable.validationReq = validationReq;
      willChangedVariable.reqContent = reqContent;
    }
  }

  async getEnumerationDefaultAndItems(mId: string) {
    const r = [] as { label: string; value: string }[];
    const modelInfo = (
      await this.controller.modelApi.modelInfo({
        projectId: this.controller.projectId,
        modelId: mId,
      })
    ).data.data;
    let isEnum = false;
    if (modelInfo.metaclass === "EnumerationLiteral") {
      const enumItemsRes =
        await this.controller.modelApi.modelPropertyGetOwnedLiteral({
          projectId: this.controller.projectId,
          modelId: mId,
        });
      if (enumItemsRes.data?.data?.length) {
        r.push(...enumItemsRes.data.data);
      }
      this.enumerationValuesMap.set(mId, r);
    } else {
      isEnum = true;
      const { value } = await this.controller.modelApi.modelPropertyByKey({
        modelId: mId,
        projectId: this.controller.projectId,
        key: PropertyKey.Enumeration_ownedLiteral,
      });
      if (value && value.length) {
        r.push(...value.map((v: any) => ({ label: v.name, value: v.id })));
      }
    }

    return {
      data: r,
      isEnum,
    };
  }

  async dealAndSendMainWinNoticeVariable(data: ClientShowSimVariableData) {
    const pathArr = data.modelIdPath.split("=");
    if (pathArr.length > 1) {
      pathArr.shift();
    }
    // let usedValue = data.value;
    let usedValue = stringNumFixToNum(data.value, 4);
    if (Number(data.value) && Math.log10(Number(data.value)) > 12) {
      const modelInfoRes = (
        await this.controller.modelApi.modelInfo({
          projectId: this.controller.projectId,
          modelId: data.value,
        })
      ).data?.data;
      if (modelInfoRes) {
        usedValue = modelInfoRes.name;
      }
    }
    this.controller.ipcService.send(
      WinName.MainWindow,
      MessageChannelType.SimVariableValueChanging,
      {
        modelIdPath: pathArr.reverse().join("="), // reverse to show
        value: usedValue,
      }
    );
  }

  dealVariableModelPathAndSend(id: string) {
    let dealingVariable = this.variableDataMap.get(id);
    const usedValue = dealingVariable?.value || "0.0";
    const modelsPath = [] as string[];
    while (dealingVariable) {
      modelsPath.unshift(dealingVariable.modelVo.id);
      if (dealingVariable.parentValueId) {
        // partproperty
        dealingVariable = this.variableDataMap.get(
          dealingVariable.parentValueId
        );
      } else if (dealingVariable.ownerSceneId) {
        dealingVariable = this.variableDataMap.get(
          dealingVariable.ownerSceneId
        );
      } else {
        dealingVariable = undefined;
      }
    }
    if (modelsPath.length) {
      this.dealAndSendMainWinNoticeVariable({
        value: usedValue as string,
        modelIdPath: modelsPath.join("="),
      });
    }
  }

  /**
   * 隐藏/显示列
   */
  showColumn(keys: string[]) {
    const cols = this.columns;
    const keySet = new Set(keys);
    cols.forEach(col => {
      if (!col.lock) {
        if (keySet.has(col.name)) {
          col.hidden = false;
        } else {
          col.hidden = true;
        }
      }
    });
  }
}
